package org.examcity.util.beanconverter;

import java.beans.PropertyDescriptor;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.examcity.util.ReflectionUtil;
import org.examcity.util.beanconverter.Rule.Field;

/**
 * Default implementation for the interface {@link Converter} that tries to convert objects
 * recursively according to the rules available via the method
 * {@link ConverterManager#findRule(Class, Class)}.
 * 
 * @author Antonio Gomes
 * 
 */
public class DefaultConverter implements Converter {

	private static final Log log = LogFactory.getLog(DefaultConverter.class);

	public Object convert(ConverterContext context, Class<?> aClass_, Object a, Class<?> bClass)
			throws Exception {
		if (a == null) {
			return null;
		}
		Class<?> aClass = (aClass_ == null) ? ReflectionUtil.getClass(a) : aClass_;
		if (!aClass.isInstance(a)) {
			throw new IllegalArgumentException(
					"Source object is not an instance of the source class");
		}
		log.debug("Converting from " + aClass.getName() + " to " + bClass.getName());
		if (bClass.isInstance(a)) {
			return a;
		}

		if (aClass == int.class || aClass == Integer.class) {
			if (aClass == int.class || aClass == Integer.class) {
				return (Integer) a;
			}
			if (bClass == long.class || bClass == Long.class) {
				return Long.valueOf((Integer) a);
			}
			if (bClass == String.class) {
				return String.valueOf((Integer) a);
			}
			throw new ConverterException("Could not convert from " + aClass.getName() + " to "
					+ bClass.getName());
		}

		if (aClass == long.class || aClass == Long.class) {
			if (aClass == int.class || aClass == Integer.class) {
				return Integer.valueOf(((Long) a).intValue());
			}
			if (bClass == long.class || bClass == Long.class) {
				return Long.valueOf((Long) a);
			}
			if (bClass == String.class) {
				return String.valueOf((Long) a);
			}
			throw new ConverterException("Could not convert from " + aClass.getName() + " to "
					+ bClass.getName());
		}

		if (aClass == String.class) {
			if (bClass == int.class || bClass == Integer.class) {
				return Integer.valueOf(Integer.parseInt((String) a));
			}
			if (bClass == long.class || bClass == Long.class) {
				return Long.valueOf(Long.parseLong((String) a));
			}
			throw new ConverterException("Could not convert from java.lang.String to "
					+ bClass.getName());
		}

		ConverterManager manager = context.getManager();
		Rule rule = manager.findRule(a.getClass(), bClass);

		if (rule == null) {
			throw new ConverterException("could not find converter from " + a.getClass() + "  to "
					+ bClass);
		}
		Converter converter0 = (rule.getConverter() != null) ? rule.getConverter() : manager
				.getDefaultConverter();
		if (converter0 == null) {
			throw new ConverterException("Could not determine converter");
		}
		Factory factory0 = rule.getFactory() != null ? rule.getFactory() : manager
				.getDefaultFactory();
		if (converter0 == null) {
			throw new ConverterException("Could not determine factory");
		}

		Object b = factory0.newInstance(a, rule.getClassB());

		for (Field f : rule.getFields()) {
			log.debug("Converting field: '" + f.getFieldA() + "' -> '" + f.getFieldB() + "'");
			Object aValue = PropertyUtils.getProperty(a, f.getFieldA());
			PropertyDescriptor bDesc = ReflectionUtil.getDescriptor(b.getClass(), f.getFieldB());
			if (bDesc == null) {
				throw new ConverterException("Could not find property '" + f.getFieldB() + "' in "
						+ b.getClass());
			}
			Converter fieldConverter = f.getConverter();
			Class<?> classA = ReflectionUtil.getClass(aValue);
			Class<?> classB = bDesc.getPropertyType();
			log.debug("from " + classA + " to " + classB);
			if (fieldConverter == null) {
				Rule r = manager.findRule(classA, classB);
				if (r == null || r.getConverter() == null) {
					fieldConverter = manager.getDefaultConverter();
				}
			}
			if (fieldConverter == null) {
				throw new ConverterException("Could not determine converter for field mapping: '"
						+ f.getFieldA() + " -> '" + f.getFieldB() + "'");
			}
			Object bValue = fieldConverter.convert(context, classA, aValue, classB);
			PropertyUtils.setProperty(b, f.getFieldB(), bValue);
		}

		return b;
	}
}
